/*-------------------------------------------------------------------------
 *
 * hmac.c
 *	  实现消息认证的密钥哈希（HMAC）
 *
 * HMAC 的后备实现，按 RFC 2104 进行规定。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/common/hmac.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include "common/cryptohash.h"
#include "common/hmac.h"
#include "common/md5.h"
#include "common/sha1.h"
#include "common/sha2.h"

/*
 * 在后端，使用 palloc/pfree 来简化错误处理。在前端，
 * 使用 malloc 以便能够将失败状态返回给调用者。
 */
#ifndef FRONTEND
#define ALLOC(size) palloc(size)
#define FREE(ptr) pfree(ptr)
#else
#define ALLOC(size) malloc(size)
#define FREE(ptr) free(ptr)
#endif

/* 错误状态集 */
typedef enum pg_hmac_errno
{
	PG_HMAC_ERROR_NONE = 0,
	PG_HMAC_ERROR_OOM,
	PG_HMAC_ERROR_INTERNAL
} pg_hmac_errno;

/* 内部pg_hmac_ctx结构 */
struct pg_hmac_ctx
{
	pg_cryptohash_ctx *hash;
	pg_cryptohash_type type;
	pg_hmac_errno error;
	const char *errreason;
	int			block_size;
	int			digest_size;

	/*
	 * 使用所有支持选项中最大的块大小。这会浪费一些
	 * 内存，但简化了分配逻辑。
	 */
	uint8		k_ipad[PG_SHA512_BLOCK_LENGTH];
	uint8		k_opad[PG_SHA512_BLOCK_LENGTH];
};

#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5C

/*
 * pg_hmac_create
 *
 * 分配一个哈希上下文。在OOM情况下失败时返回NULL。后台发出错误，而不返回。
 */
pg_hmac_ctx *
pg_hmac_create(pg_cryptohash_type fc_type)
{
	pg_hmac_ctx *fc_ctx;

	fc_ctx = ALLOC(sizeof(pg_hmac_ctx));
	if (fc_ctx == NULL)
		return NULL;
	memset(fc_ctx, 0, sizeof(pg_hmac_ctx));
	fc_ctx->type = fc_type;
	fc_ctx->error = PG_HMAC_ERROR_NONE;
	fc_ctx->errreason = NULL;

	/*
	 * 初始化上下文数据。这需要知道摘要和
	 * 块长度，这取决于使用的哈希类型。
	 */
	switch (fc_type)
	{
		case PG_MD5:
			fc_ctx->digest_size = MD5_DIGEST_LENGTH;
			fc_ctx->block_size = MD5_BLOCK_SIZE;
			break;
		case PG_SHA1:
			fc_ctx->digest_size = SHA1_DIGEST_LENGTH;
			fc_ctx->block_size = SHA1_BLOCK_SIZE;
			break;
		case PG_SHA224:
			fc_ctx->digest_size = PG_SHA224_DIGEST_LENGTH;
			fc_ctx->block_size = PG_SHA224_BLOCK_LENGTH;
			break;
		case PG_SHA256:
			fc_ctx->digest_size = PG_SHA256_DIGEST_LENGTH;
			fc_ctx->block_size = PG_SHA256_BLOCK_LENGTH;
			break;
		case PG_SHA384:
			fc_ctx->digest_size = PG_SHA384_DIGEST_LENGTH;
			fc_ctx->block_size = PG_SHA384_BLOCK_LENGTH;
			break;
		case PG_SHA512:
			fc_ctx->digest_size = PG_SHA512_DIGEST_LENGTH;
			fc_ctx->block_size = PG_SHA512_BLOCK_LENGTH;
			break;
	}

	fc_ctx->hash = pg_cryptohash_create(fc_type);
	if (fc_ctx->hash == NULL)
	{
		explicit_bzero(fc_ctx, sizeof(pg_hmac_ctx));
		FREE(fc_ctx);
		return NULL;
	}

	return fc_ctx;
}

/*
 * pg_hmac_init
 *
 * 初始化HMAC上下文。成功时返回0，失败时返回-1。
 */
int pg_hmac_init(pg_hmac_ctx *fc_ctx, const uint8 *fc_key, size_t fc_len)
{
	int			fc_i;
	int			fc_digest_size;
	int			fc_block_size;
	uint8	   *fc_shrinkbuf = NULL;

	if (fc_ctx == NULL)
		return -1;

	fc_digest_size = fc_ctx->digest_size;
	fc_block_size = fc_ctx->block_size;

	memset(fc_ctx->k_opad, HMAC_OPAD, fc_ctx->block_size);
	memset(fc_ctx->k_ipad, HMAC_IPAD, fc_ctx->block_size);

	/*
	 * 如果密钥长于块大小，则进行一次哈希以缩短它。
	 */
	if (fc_len > fc_block_size)
	{
		pg_cryptohash_ctx *fc_hash_ctx;

		/* 用于一次性缩小的临时缓冲区 */
		fc_shrinkbuf = ALLOC(fc_digest_size);
		if (fc_shrinkbuf == NULL)
		{
			fc_ctx->error = PG_HMAC_ERROR_OOM;
			return -1;
		}
		memset(fc_shrinkbuf, 0, fc_digest_size);

		fc_hash_ctx = pg_cryptohash_create(fc_ctx->type);
		if (fc_hash_ctx == NULL)
		{
			fc_ctx->error = PG_HMAC_ERROR_OOM;
			FREE(fc_shrinkbuf);
			return -1;
		}

		if (pg_cryptohash_init(fc_hash_ctx) < 0 ||
			pg_cryptohash_update(fc_hash_ctx, fc_key, fc_len) < 0 ||
			pg_cryptohash_final(fc_hash_ctx, fc_shrinkbuf, fc_digest_size) < 0)
		{
			fc_ctx->error = PG_HMAC_ERROR_INTERNAL;
			fc_ctx->errreason = pg_cryptohash_error(fc_hash_ctx);
			pg_cryptohash_free(fc_hash_ctx);
			FREE(fc_shrinkbuf);
			return -1;
		}

		fc_key = fc_shrinkbuf;
		fc_len = fc_digest_size;
		pg_cryptohash_free(fc_hash_ctx);
	}

	for (fc_i = 0; fc_i < fc_len; fc_i++)
	{
		fc_ctx->k_ipad[fc_i] ^= fc_key[fc_i];
		fc_ctx->k_opad[fc_i] ^= fc_key[fc_i];
	}

	/* tmp = H(K XOR ipad, text) */
	if (pg_cryptohash_init(fc_ctx->hash) < 0 ||
		pg_cryptohash_update(fc_ctx->hash, fc_ctx->k_ipad, fc_ctx->block_size) < 0)
	{
		fc_ctx->error = PG_HMAC_ERROR_INTERNAL;
		fc_ctx->errreason = pg_cryptohash_error(fc_ctx->hash);
		if (fc_shrinkbuf)
			FREE(fc_shrinkbuf);
		return -1;
	}

	if (fc_shrinkbuf)
		FREE(fc_shrinkbuf);
	return 0;
}

/*
 * pg_hmac_update
 *
 * 更新HMAC上下文。成功时返回0，失败时返回-1。
 */
int pg_hmac_update(pg_hmac_ctx *fc_ctx, const uint8 *fc_data, size_t fc_len)
{
	if (fc_ctx == NULL)
		return -1;

	if (pg_cryptohash_update(fc_ctx->hash, fc_data, fc_len) < 0)
	{
		fc_ctx->error = PG_HMAC_ERROR_INTERNAL;
		fc_ctx->errreason = pg_cryptohash_error(fc_ctx->hash);
		return -1;
	}

	return 0;
}

/*
 * pg_hmac_final
 *
 * 最终确定HMAC上下文。成功时返回0，失败时返回-1。
 */
int pg_hmac_final(pg_hmac_ctx *fc_ctx, uint8 *fc_dest, size_t fc_len)
{
	uint8	   *fc_h;

	if (fc_ctx == NULL)
		return -1;

	fc_h = ALLOC(fc_ctx->digest_size);
	if (fc_h == NULL)
	{
		fc_ctx->error = PG_HMAC_ERROR_OOM;
		return -1;
	}
	memset(fc_h, 0, fc_ctx->digest_size);

	if (pg_cryptohash_final(fc_ctx->hash, fc_h, fc_ctx->digest_size) < 0)
	{
		fc_ctx->error = PG_HMAC_ERROR_INTERNAL;
		fc_ctx->errreason = pg_cryptohash_error(fc_ctx->hash);
		FREE(fc_h);
		return -1;
	}

	/* H(K XOR opad, tmp) */
	if (pg_cryptohash_init(fc_ctx->hash) < 0 ||
		pg_cryptohash_update(fc_ctx->hash, fc_ctx->k_opad, fc_ctx->block_size) < 0 ||
		pg_cryptohash_update(fc_ctx->hash, fc_h, fc_ctx->digest_size) < 0 ||
		pg_cryptohash_final(fc_ctx->hash, fc_dest, fc_len) < 0)
	{
		fc_ctx->error = PG_HMAC_ERROR_INTERNAL;
		fc_ctx->errreason = pg_cryptohash_error(fc_ctx->hash);
		FREE(fc_h);
		return -1;
	}

	FREE(fc_h);
	return 0;
}

/*
 * pg_hmac_free
 *
 * 释放HMAC上下文。
 */
void pg_hmac_free(pg_hmac_ctx *fc_ctx)
{
	if (fc_ctx == NULL)
		return;

	pg_cryptohash_free(fc_ctx->hash);
	explicit_bzero(fc_ctx, sizeof(pg_hmac_ctx));
	FREE(fc_ctx);
}

/*
 * pg_hmac_error
 *
 * 返回一个静态字符串，提供有关HMAC计算过程中发生错误的详细信息。
 */
const char * pg_hmac_error(pg_hmac_ctx *fc_ctx)
{
	if (fc_ctx == NULL)
		return _("out of memory");

	/*
	 * 如果提供了原因，则依赖于它，否则退回到设置的任何错误代码。
	 */
	if (fc_ctx->errreason)
		return fc_ctx->errreason;

	switch (fc_ctx->error)
	{
		case PG_HMAC_ERROR_NONE:
			return _("success");
		case PG_HMAC_ERROR_INTERNAL:
			return _("internal error");
		case PG_HMAC_ERROR_OOM:
			return _("out of memory");
	}

	Assert(false);				/* 无法到达 */
	return _("success");
}
